package com.yhsoft.wxprogram.utils;

import com.aliyun.oss.ServiceException;
import com.github.wxpay.sdk.WXPayUtil;
import com.google.gson.Gson;
import com.yhsoft.wxprogram.constants.WxApiConstants;
import com.yhsoft.wxprogram.dto.WxChatBasePayDto;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

@Slf4j
public class WxPayCommon {

    /**
     * 封装基础通用请求数据
     *
     * @param basePayData 微信支付基础请求数据
     * @return 封装后的map对象
     */
    public static Map<String, Object> getBasePayParams(WxChatBasePayDto basePayData) {
        Map<String, Object> paramsMap = new HashMap<>();
        paramsMap.put("appid", ConstantPropertiesUtils.APPID);
        paramsMap.put("mchid", ConstantPropertiesUtils.PARTNER);
        // 如果商品名称过长则截取
        String title = basePayData.getTitle().length() > 62 ? basePayData.getTitle().substring(0, 62) :
                basePayData.getTitle();
        paramsMap.put("description", title);
        paramsMap.put("out_trade_no", basePayData.getOrderId());
        paramsMap.put("notify_url", ConstantPropertiesUtils.DOMAIN.concat(basePayData.getNotify().getType()));
        Map<String, Integer> amountMap = new HashMap<>();
        amountMap.put("total", basePayData.getPrice().multiply(new BigDecimal("100")).intValue());
        paramsMap.put("amount", amountMap);
        return paramsMap;
    }

    /**
     * 获取请求对象（Post请求）
     *
     * @param apiType   接口请求地址
     * @param paramsMap 请求参数
     * @return Post请求对象
     */
    public static HttpPost getHttpPost(WxApiConstants apiType, Map<String, Object> paramsMap) {
        // 1.设置请求地址
        HttpPost httpPost = new HttpPost(ConstantPropertiesUtils.DOMAIN.concat(apiType.getType()));

        // 2.设置请求数据
        Gson gson = new Gson();
        String jsonParams = gson.toJson(paramsMap);

        // 3.设置请求信息
        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        String authorization = getToken(ConstantPropertiesUtils.DOMAIN.concat(apiType.getType()), jsonParams);
        httpPost.setHeader("Authorization", "WECHATPAY2-SHA256-RSA2048 " + authorization);
        httpPost.setHeader("Accept", "application/json");
        return httpPost;
    }

    private static String getToken(String url, String body) {
        String nonceStr = WXPayUtil.generateNonceStr();
        long timestamp = System.currentTimeMillis() / 1000;
        String message = buildMessage(url, timestamp, nonceStr, body);
        String signature = null;
        signature = sign(message.getBytes(StandardCharsets.UTF_8));
        return "mchid=\"" + ConstantPropertiesUtils.PARTNER + "\","
                + "nonce_str=\"" + nonceStr + "\","
                + "timestamp=\"" + timestamp + "\","
                + "serial_no=\"" + ConstantPropertiesUtils.SERIALNO + "\","
                + "signature=\"" + signature + "\"";
    }

    private static String sign(byte[] message) {
        try {
            Signature sign = Signature.getInstance("SHA256withRSA");
            PrivateKey merchantPrivateKey = getPrivateKey(ConstantPropertiesUtils.KEY);
            sign.initSign(merchantPrivateKey);
            sign.update(message);
            return Base64.getEncoder().encodeToString(sign.sign());
        } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
            throw new RuntimeException(e);
        }
    }

    private static PrivateKey getPrivateKey(String filename) {
        try {
            String content = new String(Files.readAllBytes(Paths.get(filename)), StandardCharsets.UTF_8);
            try {
                String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                        .replace("-----END PRIVATE KEY-----", "")
                        .replaceAll("\\s+", "");
                KeyFactory kf = KeyFactory.getInstance("RSA");
                return kf.generatePrivate(
                        new PKCS8EncodedKeySpec(Base64.getMimeDecoder().decode(privateKey)));
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException("当前Java环境不支持RSA", e);
            } catch (InvalidKeySpecException e) {
                throw new RuntimeException("无效的密钥格式");
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static String buildMessage(String url, long timestamp, String nonceStr, String body) {
        return "POST" + "\n"
                + url + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + body + "\n";
    }

    /**
     * 解析响应数据
     *
     * @param response 发送请求成功后，返回的数据
     * @return 微信返回的参数
     */
    public static HashMap<String, String> resolverResponse(CloseableHttpResponse response) {
        try {
            // 1.获取请求码
            int statusCode = response.getStatusLine().getStatusCode();
            // 2.获取返回值 String 格式
            final String bodyAsString = EntityUtils.toString(response.getEntity());

            Gson gson = new Gson();
            if (statusCode == 200) {
                // 3.如果请求成功则解析成Map对象返回
                HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
                return resultMap;
            } else {
                if (StringUtils.isNoneBlank(bodyAsString)) {
                    log.error("微信支付请求失败，提示信息:{}", bodyAsString);
                    // 4.请求码显示失败，则尝试获取提示信息
                    HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
                    throw new ServiceException(resultMap.get("message"));
                }
                log.error("微信支付请求失败，未查询到原因，提示信息:{}", response);
                // 其他异常，微信也没有返回数据，这就需要具体排查了
                throw new IOException("request failed");
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException(e.getMessage());
        } finally {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static HttpPost getHttpPostByRefund(WxApiConstants apiType, String param) {
        // 1.设置请求地址
        HttpPost httpPost = new HttpPost(ConstantPropertiesUtils.DOMAIN.concat(apiType.getType()).concat(param));

        // 2.设置请求信息
        httpPost.setHeader("Accept", "application/json");
        String authorization = getToken(ConstantPropertiesUtils.DOMAIN.concat(apiType.getType()), "");
        httpPost.setHeader("Authorization", authorization);
        return httpPost;
    }
}
